package com.yzh.luckydraw.domain.strategy.service.algorithm;

import com.yzh.luckydraw.common.Constants;
import com.yzh.luckydraw.domain.strategy.model.vo.AwardRateInfo;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public abstract class BaseAlgorithm implements IDrawAlgorithm {
    /**
     * 斐波那契散列增量：我们要计算一个斐波那契常数，通常选择2^32，然后乘以黄金分割比例，得到哈希增量
     * 黄金分割点(√5 - 1) / 2 = 0.6180339887
     * Math.pow(2, 32) * 0.6180339887 = 1640531527 => 0x61c88647
     */
    private final int HASH_INCREMENT = 0x61c88647;
    /**
     * 数组初始化长度为128，为了能让哈希值均匀分布，数组长度必须是2的N次方
     */
    private final int RATE_TUPLE_LENGTH = 128;
    /**
     * 存放概率与奖品对应的散列结果，strategy -> rateTuple
     */
    protected Map<Long, String[]> rateTupleMap = new ConcurrentHashMap<>();
    /**
     * 奖品概率Map
     */
    protected Map<Long, List<AwardRateInfo>> awardRateInfoMap = new ConcurrentHashMap<>();

    @Override
    public void initRateTuple(Long strategyId, Integer strategyMode, List<AwardRateInfo> awardRateInfoList) {
        //判断是否初始化
        if (isExist(strategyId)) {
            return;
        }
        
        //保存奖品概率信息
        awardRateInfoMap.put(strategyId, awardRateInfoList);
        
        //非单项概率不必存入缓存，因为总体概率需要实时处理中奖概率
        if (Constants.StrategyMode.ENTIRETY.getCode().equals(strategyMode)) {
            return;
        }

        //computeIfAbsent：指定key为null或不存在时，计算并存入一个新的键值对到Map中
        String[] rateTuple = rateTupleMap.computeIfAbsent(strategyId, k -> new String[RATE_TUPLE_LENGTH]);

        int cursorVal = 0;
        for (AwardRateInfo awardRateInfo : awardRateInfoList) {
            int rateVal = awardRateInfo.getAwardRate().multiply(new BigDecimal(100)).intValue();

            //循环填充概率范围值
            for (int i = cursorVal + 1; i <= (rateVal + cursorVal); i++) {
                rateTuple[hashIdx(i)] = awardRateInfo.getAwardId();
            }
            cursorVal += rateVal;
        }
    }

    @Override
    public boolean isExist(Long strategyId) {
        return rateTupleMap.containsKey(strategyId);
    }

    /**
     * 斐波那契散列法，计算哈希索引下标
     * 使用 hashCode & (RATE_TUPLE_LENGTH - 1) 的操作，可以确保最终的结果不会超过 RATE_TUPLE_LENGTH
     * 因为RATE_TUPLE_LENGTH的值是2的整数次幂（例如4、8、16），那么这个值的二进制表示只有最高位是1，其余都是0，一共8位(10000000)
     * 而(RATE_TUPLE_LENGTH - 1)则是最高位为0，其余都是1。一共8位(01111111)
     * 在执行hashCode & (RATE_TUPLE_LENGTH - 1) 操作，因为最高位是0，所以必定不会超过128
     *
     * @param val 值
     * @return 索引
     */
    protected int hashIdx(int val) {
        //val * HASH_INCREMENT是为了散列均匀性，再次加上HASH_INCREMENT是为了当val为0时，得到的哈希值也为0。零散列会导致分布较差
        int hashCode = val * HASH_INCREMENT + HASH_INCREMENT;
        return hashCode & (RATE_TUPLE_LENGTH - 1);
    }

    /**
     * 生成百位随机抽奖码
     *
     * @return
     */
    protected int generateSecureRandomIntCode(int bound) {
        return new SecureRandom().nextInt(bound) + 1;
    }
}
